require 'rubygems'
require 'mongrel'
require 'active_support'
require 'net/http'
require 'uri'
require 'timeout'
require 'yaml'
require 'esi/cache'
require 'esi/config'
require 'esi/router'
require 'esi/invalidator'
#DEBUG=1
require 'esi/debug' if defined?(DEBUG) and DEBUG == 1

module ESI

  class Handler
    attr_reader :config

    def initialize(dispatcher)
      super()
      @config = dispatcher.config
      @router = dispatcher.router
    end

    def process(request, response)
 
      start = Time.now
      status = 200
      url = @router.url_for(request.params["REQUEST_URI"])
      parser = @config.parser(@router,@config.cache)

      params = http_params(request.params)

      proxy_error = nil


      $logger.debug "#{request.params["REQUEST_METHOD"]} => #{url}"
      tries = 0
			uri = URI.parse(url)

			path_with_query = uri.query.blank? ? uri.path : "#{uri.path}?#{uri.query}"

			proxy_request = (request.params["REQUEST_METHOD"] == "POST") ?
													Net::HTTP::Post.new( path_with_query, params ) :
													Net::HTTP::Get.new( path_with_query, params )

			proxy_connection = Net::HTTP.start(uri.host, uri.port)

			# open the conneciton up so we can start to stream the connection
			proxy_connection.request(proxy_request,request.body.read) do|proxy_response|

				status = read_status( proxy_response )

				copy_headers( response.header, proxy_response ) unless status >= 500

				if status >= 500 or !@config.enable_esi_processor?( proxy_response )
					response.start(status, true) do|head,out|

						if status >= 500
							# TODO: only report this if configured to expose it
							out << proxy_error
						end

						# proxy the 500 response
						proxy_response.read_body do|fragment|
							out << fragment
						end

					end
				else
					begin
						# we detected the surrogate wants us to process the response checking for ESI 
						# tags.

						response.header["Transfer-Encoding"] = "chunked"
						# this is the important part, rather then send the whole document back we send in chunks
						# each fragment is it's own chunk, this does mean we require http 1.1
						header = Mongrel::Const::STATUS_FORMAT % [status, Mongrel::HTTP_STATUS_CODES[status]]
						header.gsub!(/Connection: close\r\n/,'')
						response.header.out.rewind
						header << response.header.out.read + Mongrel::Const::LINE_END
						header.gsub!(/Status:.*?\r\n/,'')
						response.write( header )

						#puts header

						parser.process_io( :input_stream => proxy_response,
															 :request_params => request.params,
															 :http_params => params ) do |buffer|
							# send a new chunk
							size = buffer.size
							chunk_header = "#{"%x" % size}" + Mongrel::Const::LINE_END
							#puts chunk_header.inspect
							response.write( chunk_header )  # write the chunk size
							#puts buffer.inspect
							response.write( buffer + Mongrel::Const::LINE_END )  # write the chunk
						end
					rescue => e
            status, error = error_response(e,url)
						response.write( error )
					end
					response.write( "0\r\n\r\n" )
					response.done = true
				end
			end # end request

		rescue Timeout::Error => timeout
			tries += 1
			retry if tries < 4
		rescue => e
			status = error_response(e,url).first
		ensure

      $request_logger.debug "\nCompleted => #{url}, #{Time.now - start} seconds with status #{status}"

    end

protected

    def error_response(e,url)
      status = 500
      error = "<h1>Internal Server Errror</h1><h4>Failed while requesting => '#{url}'</h4>\n<pre>#{e.message}#{e.backtrace.join("\n")}</pre>"
      $logger.error e.backtrace
      $logger.error error
      [status, "<html><body>#{error}</body></html>"]
    end

    def read_status(response)
      Net::HTTPResponse::CODE_TO_OBJ.select { |k,v| v == response.class }.first[0].to_i rescue 500
    end

    def http_params(params)
      updated_params = {}
      params.each do|k,v|
        k = k.split('_').collect { |t| t.humanize }.join('-')
        if k[0,5] =='Http-'
          k[0,5] = ''
          updated_params[k] = v
        end
      end
      updated_params
    end

    def copy_headers(head,response)
      # copy the response headers, except Content-Length let HttpResponse set that,
      # since it's likely going to increase with esi requests
      response.each_capitalized do |k,v|
        # for Set-Cookie we need to split on ,
        if k == "Set-Cookie"
          v.split(/,/).each do|cookie|
            head["Set-Cookie"] = cookie # mongrel is case sensitive about handling duplicates
          end
        else
          head[k] = v unless k == "Content-Length" or k == "Surrogate-Control"
        end
      end
    end

  end # Handler

end # ESI
